/**
 * Copyright (C) 2008+ Spark Zheng
 *
 * @file	src/conf.c
 * @brief
 *	config module, config file must like:
 *		#this is an annotation
 *		key1 = value1
 *		key2 = value2 \
 *		       value2-continued (need notice the space)
 *		key3 = value3-1, value3-2
 * 
 * @see		dlib
 * @module      dlib
 * @history
 *      version 0.0.1 2008-08-18, Spark init conf module.
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include "conf.h"
#include "str.h"
#include "hash.h"
#include "mempool.h"

/**
 * _conf_compare
 * @brief compare function for conf module passed to htable module.
 */
static int _conf_compare(const void *one, const void *two);

/**
 * _conf_hash
 * @brief hash function for conf module passed to htable module.
 */
static uint32_t _conf_hash(const void *k, void *other);

/**
 * _conf_load_file
 * @brief load the conf file, and fill in htable, return 0 or err.
 * @param file: conf file
 * @param dict: chtable structure.
 */
static int _conf_load_file(const char *file, htable_t *dict);

/**
 * _conf_release_val
 * @brief release the htable nodes.
 * @param val: the value stored in htable nodes
 */
static void _conf_release_val(void *val);

static int _conf_compare(const void *one, const void *two)
{
	const char *s_one = (const char *)one;
	const char *s_two = (const char *)two;
	
	while ((*s_one | *s_two) != 0) {
		if (*s_one < *s_two)
			return -1;
		else if (*s_one > *s_two)
			return 1;
		else {
			s_one++;
			s_two++;
		}
	}
	
	return 0;
}

static uint32_t _conf_hash(const void *k, void *other)
{
	const char *key = (const char *)k;
	uint32_t key_len = strlen(key);
	UNUSED_PARAM(other);
	return one_at_a_time_hash(key, key_len);
}

static void _conf_release_val(void *val)
{
	dlib_conf_t *t = (dlib_conf_t *)val;
	
	if (t->str_table.value != NULL) {
		dfree(t->str_table.value);
		t->str_table.value = NULL;
	}
	
	return;
}

static int _conf_load_file(const char *file, htable_t *dict)
{
	int last = 0;
	int line = 0;
	int line_len = 0;
	char *equal = NULL;
	char *key = NULL;
	char *value = NULL;
	int str_val_len = 0;

	FILE *cfp;
	char buffer[BUF_LEN + 1] = {0};
	void *n = NULL;

	if (file == NULL || dict == NULL) {
		return -1;
	}

	/* begin load config file */
	if ((cfp = fopen(file, "r")) == NULL) {
		fprintf(stderr, "CONF ERR: fopen error,unable to open %s: %s\n",
			file, strerror(errno));
		return -10;
	}

	while (fgets(buffer + last, BUF_LEN - last, cfp) != NULL) {
		line++;
		line_len = strlen(buffer) - 1;
		/* unexpected too long lines */
		if (buffer[line_len] != '\n') {
			fprintf(stderr, "CONF ERR: input line %d " \
				"in file %s is too long.\n", line, file);
			fclose(cfp);
			return -11;
		}
		/* comment-line */
		if (buffer[0] == '#' || (buffer[0] == '/' && buffer[1] == '/')) {
			continue;
		}

		while (line_len >= 0 && (buffer[line_len] == '\n'
					 || isspace(buffer[line_len]))) {
			buffer[line_len] = '\0';
			line_len--;
		}
		/* all space charactor */
		if (line_len == -1) {
			continue;
		}
		/* mutli-line */
		if (buffer[line_len] == '\\') {
			last = line_len;
			continue;
		} else {
			last = 0;
		}
		/* normal-line */
		if ((equal = strchr(buffer, '=')) == NULL) {
			fprintf(stderr, "CONF ERR: input line %d " \
				"in file %s is unexpected.\n", line, file);
			fclose(cfp);
			return -12;
		}
		key = buffer;
		value = equal + 1;
		*equal = '\0';

		str_trim(key);
		if (*key == 0 || (n = ht_get(dict, key)) == NULL) {
			fprintf(stderr, "CONF ERR: unknow item at " \
				"line %d in file %s.\n", line, file);
			fclose(cfp);
			return -13;
		}
		
		str_trim(value);
		if (*value == 0) {
			fprintf(stderr, "CONF WARN: unvalid value for item " \
				"%s at line %d in file %s, Just ignore it.\n", \
				key, line, file);
			continue;
		}

		if (((dlib_conf_t *)n)->str_table.value != NULL) {
			fprintf(stderr, "CONF WARN: twice item %s at " \
				"line %d in file %s, first take " \
				"effect.\n", key, line, file);
			continue;
		}
		
		str_val_len = strlen(value);
		((dlib_conf_t *)n)->str_table.value = (char *)
			dmalloc(str_val_len + 1);
		memcpy(((dlib_conf_t *)n)->str_table.value, value, str_val_len);
		(((dlib_conf_t *)n)->str_table.value)[str_val_len] = 0;
	}/* end while fgets */

	fclose(cfp);
	
	return 0;
}

htable_t *dlib_conf_init(const char *file, dlib_conf_t *t, uint32_t len,
			 uint32_t dict_len)
{
	uint32_t i;
	htable_t *dict;

	if (file == NULL || t == NULL) return NULL;

	/* generate htable, need release_val */
	dict = ht_init(dict_len, _conf_hash, NULL, _conf_compare, 0,
		       _conf_release_val);
	if (dict == NULL) return NULL;

	for (i = 0; i < len; i++) {
		/* set default table value into hashtable */
		ht_set(dict, t[i].str_table.name, t + i);
	}

	/* load file and set file value into hashtable */
	if (_conf_load_file(file, dict) < 0) {
		dlib_conf_exit(dict);
		return NULL;
	}
	
	return dict;
}

int dlib_conf_get_integer(htable_t *dict, const char *key, long *integer)
{
	long ret = 0;
	void *n = NULL;
	
	if (dict == NULL || key == NULL || integer == NULL) return -1;
	
	n = ht_get(dict, key);
	if (n == NULL) return -10;
	
	if (((dlib_conf_t *)n)->int_table.value == NULL) {
		*integer = ((dlib_conf_t *)n)->int_table.defval;
	} else {
		ret = atol(((dlib_conf_t *)n)->int_table.value);
		if (ret < ((dlib_conf_t *)n)->int_table.min ||
		    (((dlib_conf_t *)n)->int_table.max != 0 &&
		     ret > ((dlib_conf_t *)n)->int_table.max)) {
			fprintf(stderr, "CONF WARN: input %s is "
					"illegal, use default value.\n", 
					((dlib_conf_t *)n)->int_table.value);
			*integer = ((dlib_conf_t *)n)->int_table.defval;
		} else {
			*integer = atol(((dlib_conf_t *)n)->int_table.value);
		}
	}
	
	return 0;
}

char *dlib_conf_get_string(htable_t *dict, const char *key, int type)
{
	void *n = NULL;
	char *buffer = NULL;
	int str_len = 0;
	
	if (dict == NULL || key == NULL) return NULL;
	
	n = ht_get(dict, key);
	if (n == NULL) return NULL;
	
	if (type == CONF_STR_MOVE) { /* MOVE */
		if (((dlib_conf_t *)n)->str_table.value == NULL) {
			buffer = ((dlib_conf_t *)n)->str_table.defval;
		} else {
			str_len = strlen(((dlib_conf_t *)n)->str_table.value);
			
			if (str_len < ((dlib_conf_t *)n)->str_table.min ||
			    (((dlib_conf_t *)n)->str_table.max != 0 && 
			     str_len > ((dlib_conf_t *)n)->str_table.max)) {
				fprintf(stderr, "CONF WARN: input %s is "
					"illegal, use default value.\n", 
					((dlib_conf_t *)n)->str_table.value);
				buffer = ((dlib_conf_t *)n)->str_table.defval;
			} else {
				buffer = ((dlib_conf_t *)n)->str_table.value;
				((dlib_conf_t *)n)->str_table.value = NULL;
			}
		}
	} else { /* COPY */
		if (((dlib_conf_t *)n)->str_table.value == NULL) {
			buffer = ((dlib_conf_t *)n)->str_table.defval;
		} else {
			str_len = strlen(((dlib_conf_t *)n)->str_table.value);
			
			if (str_len < ((dlib_conf_t *)n)->str_table.min ||
			    (((dlib_conf_t *)n)->str_table.max != 0 && 
			     str_len > ((dlib_conf_t *)n)->str_table.max)) {
				fprintf(stderr, "CONF WARN: input %s is "
					"illegal, use default value.\n", 
					((dlib_conf_t *)n)->str_table.value);
				buffer = ((dlib_conf_t *)n)->str_table.defval;
			} else {
				buffer = (char *)dmalloc(str_len + 1);
				memcpy(buffer,
				       ((dlib_conf_t *)n)->str_table.value,
				       str_len);
				buffer[str_len] = 0;
			}
		}
	}/* end if type */
	
	return buffer;
}

void dlib_conf_exit(htable_t *dict)
{
	if (dict == NULL) return;
	ht_exit(dict);
	return;
}
